from django.template import RequestContext
from django.shortcuts import render_to_response
from django.contrib.auth.decorators import user_passes_test
from django.contrib.auth.models import User
from django.db import models
from django.http import Http404
from django.core.paginator import Paginator, InvalidPage, EmptyPage
from django.utils.http import urlencode
from django.utils.safestring import mark_safe
from django.shortcuts import get_object_or_404
from skoolmaster.general.filters import Filter

class InsufficientAuthorisation(Exception):
  pass

class InvalidPluginClass(Exception):
  """
  This exception gets raised when the plugin class does not inherit
  from a predefined superclass.
  """
  pass

class Actor(object):
  """
  An actor is a thin wrapper around a request object. It contains
  the current request and the user profile.
  """
  is_valid = False
  request = None
  user_profile = None
  
  def __init__(self, request):
    """
    Class constructor.
    
    @param request: A valid HttpRequest
    """
    if request is not None:
      assert hasattr(request, 'user'), "Authentication middleware is required!"
      self.request = request
      self.user_profile = request.user.get_profile() if isinstance(request.user, User) else None
      self.is_valid = True

class CodeListTable(object):
  """
  A simple structure that links an ID to 
  a form used of a model used as a code-list.
  """
  def __init__(self, id, label, formClass):
    self.id = id
    self.label = label
    self.formClass = formClass

def load_plugin(module_path, required_super = None):
  """
  A helper function to load a plugin (class) given its full path
  as a string. Paths are valid Python module names that must be
  importable at time of calling this method.
  
  @param module_path: A valid module path that ends in a class name
  @param required_super: Optional required superclass
  @return: The given class
  """
  module = module_path[0:module_path.rfind('.')]
  className = module_path[module_path.rfind('.') + 1:]
  m = __import__(module, globals(), locals(), [className], -1)
  cls = m.__dict__[className]
  
  if required_super is not None and not issubclass(cls, required_super):
    raise InvalidPluginClass
  
  return cls

def render_template(request, template, mapping = None):
  """
  A helper function for rendering a template with RequestContext set.
  """
  mapping = mapping or {}
  return render_to_response(template, mapping, context_instance = RequestContext(request))

def student_required(f):
  """
  A decorator for ensuring that the user is a student.
  """
  return user_passes_test(lambda u: u.is_authenticated() and u.get_profile().is_student())(f)

def lecturer_required(f):
  """
  A decorator for ensuring that the user is a lecturer.
  """
  return user_passes_test(lambda u: u.is_authenticated() and u.get_profile().is_lecturer())(f)

def administrator_required(f):
  """
  A decorator for ensuring that the user is an administrator.
  """
  return user_passes_test(lambda u: u.is_authenticated() and u.get_profile().is_administrator())(f)

def maintainer_required(f):
  """
  A decorator for ensuring that the user is a maintainer.
  """
  return user_passes_test(lambda u: u.is_authenticated() and u.get_profile().is_maintainer())(f)

def lecturer_or_administrator_required(f):
  """
  A decorator for ensuring that the user is an administrator or an lecturer.
  """
  return user_passes_test(lambda u: u.is_authenticated() and 
                          (u.get_profile().is_lecturer() or u.get_profile().is_administrator()))(f)

def student_or_administrator_required(f):
  """
  A decorator for ensuring that the user is an administrator or an lecturer.
  """
  return user_passes_test(lambda u: u.is_authenticated() and 
                          (u.get_profile().is_student() or u.get_profile().is_administrator()))(f)

def check_lecturer_subjects(request, subject):
  """
  Ensures that a lecturer is only granted access when the subject
  in question is one of his own.
  
  @param request: Current request
  @param subject: Subject to check against
  """
  from skoolmaster.syllabus.models import Subject
  
  if not isinstance(subject, Subject):
    subject = get_object_or_404(Subject, pk = subject)
  
  # Administrators have complete access
  if request.user.get_profile().is_administrator():
    return
  
  # Perform checks for lecturers that are not administrators
  if subject not in request.user.lecture_subjects.all():
    raise InsufficientAuthorisation

def show_model_results_view(request, template, queryset = None, fields = [], show_search = True, printable = True, search_logic = None, vars = {}, show_filters = None, default_sort_field = None):
  """
  A generic implementation of model results views.
  """
  if default_sort_field is None:
    order_field = fields[0]['name'] if fields[0]['name'] else None
  else:
    order_field = default_sort_field
  
  query = request.GET.get('q', '')
  order_field = request.GET.get('o', order_field)
  order_asc = (order_field[0] != '-') if order_field else True
  
  # Produce the actual queryset
  if show_search:
    queryset = search_logic(query, queryset)
  
  # Ordering
  if order_field:
    queryset = queryset.order_by(order_field)
  
  # Pagination
  try:
    page = int(request.GET.get('p', '1'))
  except ValueError:
    page = 1
  
  variables = {}
  query_args = {
    'q' : query,
    'o' : order_field
  }
  
  if show_filters:
    model = queryset.model
    filters = []
    
    for attr in show_filters:
      f = Filter.create(model._meta.get_field(attr), model, request)
      f.update_query_args(query_args)
      queryset = f.restrict(queryset)
      filters.append(f)
    
    variables['filters'] = filters
    variables['filter_html'] = mark_safe("".join([f.get_output(query_args) for f in filters]))
  
  query_args_order = query_args.copy()
  del query_args_order['o']
  
  # Pagination    
  if printable and request.GET.get('print'):
    paginator = Paginator(queryset, queryset.count())
  else:
    paginator = Paginator(queryset, 50)
  try:
    queryset = paginator.page(page)
  except (EmptyPage, InvalidPage):
    queryset = paginator.page(paginator.num_pages)

  # Add method for getting the serial number
  def get_serial_number():
    queryset._serial_number += 1
    return queryset._serial_number + (page - 1) * 50
  
  queryset._serial_number = 0
  queryset.get_serial_number = get_serial_number
  
  get_args = request.GET.copy()
  for query_arg in query_args.keys() + ["p"]:
    if query_arg in get_args:
      del get_args[query_arg]

  variables.update({
    'query'            : query,
    'queryset'         : queryset,
    'fields'           : fields,
    'order_field'      : order_field if order_asc else order_field[1:],
    'order_asc'        : order_asc,
    'show_search'      : show_search,
    'query_args'       : get_args.urlencode() + "&" + urlencode(query_args),
    'query_args_order' : get_args.urlencode() + "&" + urlencode(query_args_order),
    'get_args'         : get_args.iterlists(),
    'print_view'       : request.GET.get('print', '') if printable else '',
    'printable'        : printable
  })
  variables.update(vars)
  return render_template(request, template, variables)

def read_only(form, modelClass):
  """
  Sets all primary key fields to read only.
  """
  for field in filter(lambda field: field.primary_key, modelClass._meta.fields):
    if form.fields.has_key(field.name):
      form.fields[field.name].widget.attrs.update({'readonly' : 'readonly'})
